using System;
using System.Xml.Serialization;

namespace com.exceptionalsoftware.spacetime.KML
{
	[Serializable()]
    [XmlType(TypeName = "CoordinatesType")]
    public class Coordinate
	{
        private double lat, lon, alt;

        public Coordinate()
        {
            this.initialize(0, 0, 0);
        }

        public Coordinate(double longitude, double latitude)
        {
            this.initialize(longitude, latitude, 0);
        }

        public Coordinate(double longitude, double latitude, double altitude)
        {
            this.initialize(longitude, latitude, altitude);
        }

        public Coordinate(string coord)
        {
            this.initialize(coord);
        }

        private void initialize(double longitude, double latitude, double altitude)
        {
            this.Altitude = altitude;
            this.Latitude = latitude;
            this.Longitude = longitude;
        }

        private void initialize(string coord)
        {
            try
            {
                string[] components = coord.Split(new char[] { ',' });
                if (components.Length >= 2)
                {
                    this.Longitude = System.Double.Parse(components[0]);
                    this.Latitude = System.Double.Parse(components[1]);
                    if (components.Length >= 3)
                    {
                        this.Altitude = System.Double.Parse(components[2]);
                    }
                    else
                    {
                        this.Altitude = 0;
                    }
                }
            }
            catch
            {
                this.initialize(0, 0, 0);
            }
        }

        [XmlText(DataType = "string")]
        public string Serialized
        {
            get { return this.lon + "," + this.lat + "," + this.alt; }
            set { this.initialize(value); }
        }

        [XmlIgnore()]
        public double Latitude
        {
            get { return this.lat; }
            set { this.lat = Coordinate.NormalizeLatitude(value); }
        }

        [XmlIgnore()]
        public double Longitude
        {
            get { return this.lon; }
            set { this.lon = Coordinate.NormalizeLongitude(value); }
        }

        [XmlIgnore()]
        public double Altitude
        {
            get { return this.alt; }
            set { this.alt = Coordinate.NormalizeAltitude(value); }
        }

        public static double NormalizeLatitude(double latitude)
        {
            return Coordinate.NormalizeToFixedLimits(latitude, -90, 90);
        }

        public static double NormalizeLongitude(double longitude)
        {
            return Coordinate.NormalizeToRepeatingLimits(longitude, -180, 180);
        }

        public static double NormalizeAltitude(double altitude)
        {
            if (altitude < 0)
                altitude = 0;
            return altitude;
        }

        public override bool Equals(object obj)
        {
            if (!obj.GetType().Equals(typeof(Coordinate)))
            {
                return false;
            }

            Coordinate c = (Coordinate)obj;
            double dlon = Math.Abs(this.lon - c.lon);
            if (dlon > 0.000001)
                return false;

            double dlat = Math.Abs(this.lat - c.lat);
            if (dlat > 0.000001)
                return false;

            double dalt = Math.Abs(this.alt - c.alt);
            if (dalt > 0.000001)
                return false;

            return true;
        }

        public override string ToString()
        {
            return "(" + this.Serialized + ")";
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode() * 0xc007 + 0xd1ae;
        }

        public static double NormalizeToFixedLimits(double value, double minimum, double maximum)
        {
            double min = Math.Min(minimum, maximum);
            double max = Math.Max(minimum, maximum);

            if (value < min)
                value = min;

            if (value > max)
                value = max;

            return value;
        }

        public static double NormalizeToRepeatingLimits(double value, double minimum, double maximum)
        {
            double min = Math.Min(minimum, maximum);
            double max = Math.Max(minimum, maximum);

            double range = max - min;
            if (range == 0)
                return value;

            while (value < min)
            {
                value += range;
            }

            while (value > max)
            {
                value -= range;
            }

            return value;
        } 
    }
}